這是我個人的使用偏好,而且是以抽象資料型別的使用方式來理解 vuex 的使用方式。也許,我是說也許啦!和官網的不太一樣。
昨天介紹了物件設計的核心原則,就是不要直接修改資料,務必要透過抽象行為來修改,以保持彈性與添加限制的同時,可以維護概念整體性。
這個原則,在後端也適用於 MVC 的 Module
Mutations | Vuex 的開章明義就說,想要改變 state ,就要 over my dead body (透過 mutation)。
The only way to actually change state in a Vuex store is by committing a mutation.
所以,其實 mutation 本身就隱含著 setter 的任務。必須單純的修改值,並且適時的可以加入定義域限制。
「mutation 是唯一可以改變 state 的角色」,意思是說改變的同時要可以觸發修改畫面。所以,修改的原則,也是要符合 immutable,或者用 Vue 提供的 API Vue.set(obj, 'newProp', 123)
Use
Vue.set(obj, 'newProp', 123)
, orReplace that Object with a fresh one. For example, using the object spread syntax (opens new window)we can write it like this:
state.obj = { ...state.obj, newProp: 123 }
不過我覺得「靠自己,好自在」。所以只要修改值,就要注意 immutable,以及觸發自動修改畫面的機制 (符合 MVVM 的精神)
通常 mutation 是在接受 Web API 之後將 Web API 的資料 (通常是 GET 的 API) 儲存到 state。
而這個步驟就足以決定你的 vuex 管理是否簡單或複雜。
請遵守這些原則
命名不要用動詞
命名要與 getters 同名
命名要注意單數/複數
因為使用的時候是
this.$store.commit('user', data)
this.$store.commit('users', data)
光這一行,你看得出什麼樣的資訊?看不出什麼樣的資訊呢?
看得出這樣的資訊就夠了。
至於 set 進 user 需不需要定義域的阻擋邏輯,通常 API 來的資料,都已經通過畫面的欄位驗證以及後端在接收到 POST 的時的驗證,資料經過了這些驗證而進入資料庫,所以在取得資料時,其實應該相信這些機制的作用,不用在這個環節再阻擋或驗證一次。
但如果,你的 commit 是直接透過畫面把值設定進來,也許需要在 Mutation 加入驗證,因為要確保 state 是正確的,才不會影響 getters 在輸出資料時,還要寫更多的邏輯處理。
官網的 Actions 說,它可以寫非同步行為,而且要用 action 呼叫 mutation
mutation 不要寫非同步行為
不要用 action 直接改 state
官網說建議要透過 action 呼叫 mutation 的理由,是萬一你要非同步的使用 mutation 的話,就可以直接添加在 action。
我自己的解讀是「如果沒有要非同步,直接 commit 也是可以」
所以,我自己的用法會是「同步行為與非同步行為,分成 action 和 muation」
如果看見這樣寫
this.$store.dispatch('fetchUsers')
就是它要發一個 API 去請求 User 的列表,並且會在裡面呼叫 commit('users', res.data)
將 response 的 body 儲存在 state 裡。
如果看見這樣寫
this.$store.commit('users', users)
就只是將資料儲存到 state 裡。
如果看見這樣寫
this.$store.dispatch('users', users)
我會不知道,這是非同步的還是同步的行為,中間經造了什麼將 users 儲存到 state 裡。
請遵守這些原則
命名要加上動詞
以 User Module 為例 action 的命名會依 API 的用途為主。CRUD 的話已經有一定的命名原則。
行為 | 命名 | 實際使用 |
---|---|---|
新增 | createUser | $store.dispatch('createUser') |
讀取 | fetchUser | $store.dispatch('fetchUser', id) |
讀取 | fetchUsers | $store.dispatch('fetchUsers') |
修改 | updateUser | $store.dispatch('updateUser', id) |
刪除 | deleteUsers | $store.dispatch('deleteUsers') |
刪除 | deleteUser | $store.dispatch('deleteUser', id) |
命名要具體的描述行為,不需與 API 相同
有時候網站會有一些留言的機制。而留言的目的也許有不同的 type
有時 API 的開法, type 並不是參數,而是 path 的一部份
POST
/message/issue
POST
/message/comments
這個 issues 和 comments 就會把 actions 設計成
$store.dispatch('createMessage', { type, body })
comments | issue
這樣 actions 的命名,是一種「使用非同步行為」的描述,而不只是非同步行為轉變形式的樣子
命名要注意單數/複數
名詞的部份和 commit 一樣,而且也是隱喻著要 commit 到哪裡去。
根據 state 的狀態衍生出其它的資料。
可以理解是一種共用的 computed 也可以理解是一種「整理資料的資料邏輯」
如果今天,在修改使用者 (ex: id=1
) user 的表單中,需要設定該 user 的主管,這個主管會是由一個下拉式選單來顯示。
那麼資料該怎麼取得呢?
$store.dispatch('fetchUser', 1)
$store.dispatch('fetchUsers')
$store.getters.leaders
leaderOptions
(在 computed 裡或 getters 也可以)取得主管的下拉式選單中,有一個 $store.getters.leaders
getters: {
leaders(state, getters) {
const departments = getters.departments;
return state.users.filter(user => departments.some(department => department.leader === user))
}
}
將整理資料的程式碼寫在 getters ,而不是在 mutation 的時候整理資料,有幾個好處
mutation
第一個參數是 state (所屬的 module 裡的 state)
第二個參數是 payload (帶什麼新東西要來修改值)?
mutation: {
user (state, payload) {
state.user = payload
}
}
getters
第一個參數是 state (所屬的 module 裡的 state)
第二個參數是跨 module 的 getters (不直接取得 state)
getters: {
leaders(state, getters) {
const departments = getters.departments;
return state.users.filter(user => departments.some(department => department.leader === user))
}
}
actions
第一個參數是 context 跨 module 取得 dispatch, commit, getters (不直接取得 state)
第二個參數這次傳進來的參數,用起來和 mutation 的 payload 一樣。
actions: {
fetchUser ({ dispatch, commit, getters }, user_id) {
// ...
}
}
透過觀察參數可以發現 mutation 和 getters 的設計,修改/讀取都是以 state 為核心。而 actions 比較像是面對這些 module 的獨立 function 。有點像是後端的 controller。
所以,我在使用 vuex 的資料夾規畫上面。只會把 actions.js 獨立成一個檔案。state
, mutation
, getters
這三樣,會是放在一起的,形成了一個有自己 getter/setter 的抽象資料型別。